/*
 * $Id: MagnetMonitorUtility.java,v 1.2 2009/06/22 15:50:28 afaichun Exp $
 * 
 * Copyright (C) 2008 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.processor.magmon;

import java.text.DecimalFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.StringTokenizer;

import com.ge.healthcare.autosc.processor.exception.MagnetDataFormatException;

/**
 * Utility class for the magnet monitor.  This contains a number of utility
 * methods.  They are separate from the other magnet monitor classes to
 * simplify testing.
 * @author 212042946
 *
 */
public class MagnetMonitorUtility {
    /**
     * Format used for mimicing the C %02d format.  Because the format is
     *  based on the locale, the constructor is expensive.  Therefore, a
     *  single format object is used for each instance of this class.
     */
    private DecimalFormat the02dFormat = new DecimalFormat( "00" );

    /**
     * Format used for mimicing the C %04d format.  Because the format is
     *  based on the locale, the constructor is expensive.  Therefore, a
     *  single format object is used for each instance of this class.
     */
    private DecimalFormat the04dFormat = new DecimalFormat( "0000" );

    /**
     * Format used for mimicing the C % 9.3f format.  Because the format is
     *  based on the locale, the constructor is expensive.  Therefore, a
     *  single format object is used for each instance of this class.
     */
    private DecimalFormat theBlank93fFormat =
        new DecimalFormat( "####0.000;-###0.000" );

    /**
     * Calendar used for converting a C timestamp, seconds since midnight
     *  1-Jan-1970 UTC, to its component pieces.  Because the calendar is
     *  based on the locale, the constructor is expensive.  Therefore, a
     *  single calendar object is used for each instance of this class.
     */
    private GregorianCalendar theCalendar = new GregorianCalendar();

    /**
     * Creates a new MagmonUtility instance.
     */
    public MagnetMonitorUtility()
    {
        super();
    }

    /**
     * Generate a line in magnet alarm log format from a line in alarm log
     *  format.
     *
     * @param currentLine The current line in alarm log format.
     *
     * @return The line in magnet alarm log format, which only occurs if
     *  the current line is a low limit or a high limit record.
     *  A null indicates that there is no translation available.
     *
     * @exception MagDataFormatException If an error is detected in the
     *  format of the source alarm data.
     */
    public String getAlarmLineFrom( String currentLine )
        throws MagnetDataFormatException
    {
        String[] componentStrings = new String[6];
        breakString( componentStrings, currentLine, ':' );
        if ( componentStrings[5] == null )
        {
            return null;
        }
        boolean doingHighLimit = false;
        String alarmType = componentStrings[1].toLowerCase();
        if ( alarmType.equals( "high limit" ) )
        {
            doingHighLimit = true;
        }
        else if ( alarmType.equals( "low limit" ) )
        {
            doingHighLimit = false;
        }
        else
        {
            return null;
        }
        StringBuffer buffer = new StringBuffer( 80 );
        appendBrokenOutTimeTo( buffer, componentStrings[0], currentLine );
        buffer.append( "  " );
        String commentField = componentStrings[2].trim();
        appendLeftJustifiedTo( buffer, commentField, 28 );
        final float parameterValue =
            getFloatFrom( componentStrings[4], "Parameter", currentLine );
        final float limitValue =
            getFloatFrom( componentStrings[5], "Limit", currentLine );
        if ( doingHighLimit )
        {
            buffer.append( " XXXXX.XXX  " );
            appendBlank93fTo( buffer, parameterValue );
            buffer.append( " *" );
            appendBlank93fTo( buffer, limitValue );
            buffer.append( '*' );
        }
        else
        {
            buffer.append( '*' );
            appendBlank93fTo( buffer, limitValue );
            buffer.append( "* " );
            appendBlank93fTo( buffer, parameterValue );
            buffer.append( "  XXXXX.XXX" );
        }
        return buffer.toString();
    }

    /**
     * Break apart a delimited string into its component parts, placing
     *  the component strings into an array.  If the array has fewer
     *  elements than the number of component strings, ignore the extra
     *  component strings.  If the array has more elements than the number
     *  of component strings, set the extra elements to null.
     *
     * @param componentStrings The component strings making up the
     *  delimited string.
     *
     * @param sourceString The source string, which will be broken into
     *  its component strings based on the specified delimiter character.
     *
     * @param delimiter The delimiter used to break the source string
     *  into its component parts.
     */
    private void breakString( String[] componentStrings,
                              String sourceString,
                              char delimiter )
    {
        StringTokenizer theTokenizer =
            new StringTokenizer( sourceString, String.valueOf(delimiter) );
        for ( int idx = 0; idx < componentStrings.length; idx += 1 )
        {
            String currentToken = null;
            if ( theTokenizer.hasMoreTokens() )
            {
                currentToken = theTokenizer.nextToken();
            }
            componentStrings[idx] = currentToken;
        }
    }

    /**
     * Append the specified time value to the specified buffer in the
     *  format "yyyy-mm-dd hh:mm".
     *
     * @param buffer The buffer to which the append the specified time value.
     *
     * @param integerTimeString The time value as an ASCII string.
     *  The integer value is the time in UNIX format, which is the number
     *  of seconds since midnight 1-Jan-1970 UTC.
     *
     * @param wholeLine The line containing the specified time value.
     *
     * @exception MagDataFormatException If the specified time value is
     *  not a valid integer.
     */
    private void appendBrokenOutTimeTo( StringBuffer buffer,
                                        String integerTimeString,
                                        String wholeLine )
        throws MagnetDataFormatException
    {
        int integerTime = 0;
        try
        {
            integerTime = Integer.parseInt( integerTimeString, 10 );
        }
        catch ( NumberFormatException e )
        {
        	StringBuffer message = new StringBuffer();
        	message.append("Time value not a valid integer:");
        	message.append("{time=").append(integerTimeString).append("}");
        	message.append("{line=").append(wholeLine).append("}");
            throw new MagnetDataFormatException(message.toString());
        }
        appendBrokenOutTimeTo( buffer, integerTime, '-', ' ', ':' );
    }

    /**
     * Appends a string representing a timestamp as local time, in the
     *  format "yyyy.mm.dd.hh.mm", to the specified buffer.  The periods
     *  in the format are replaced by the specified delimiter characters.
     *
     * @param buffer The buffer to which to append the specified time value.
     *
     * @param integerTime The timestamp in UNIX format, which is the number
     *  of seconds since midnight 1-Jan-1970 UTC.
     *
     * @param yearDelimiter The delimiter between the year and the month
     *  and between the month and the day.
     *
     * @param dayDelimiter The delimiter between the day and the hour.
     *
     * @param hourDelimiter The delimiter between the hour and the minute.
     */
    public void appendBrokenOutTimeTo( StringBuffer buffer,
                                       int integerTime,
                                       char yearDelimiter,
                                       char dayDelimiter,
                                       char hourDelimiter )
    {
        appendBrokenOutTimeTo( buffer,
                               integerTime,
                               yearDelimiter,
                               dayDelimiter,
                               hourDelimiter,
                               false );
    }

    /**
     * Appends a string representing a timestamp as local time, in the
     *  format "yyyy.mm.dd.hh.mm[.ss]", to the specified buffer.  The periods
     *  in the format are replaced by the specified delimiter characters.
     *
     * @param buffer The buffer to which to append the specified time value.
     *
     * @param integerTime The timestamp in UNIX format, which is the number
     *  of seconds since midnight 1-Jan-1970 UTC.
     *
     * @param yearDelimiter The delimiter between the year and the month
     *  and between the month and the day.
     *
     * @param dayDelimiter The delimiter between the day and the hour.
     *
     * @param hourDelimiter The delimiter between the hour and the minute.
     *
     * @param includeSeconds True if seconds are to be included, using the
     *  hour delimiter between the minutes and the seconds.  If false, omit
     *  the seconds.
     */
    public void appendBrokenOutTimeTo( StringBuffer buffer,
                                       int integerTime,
                                       char yearDelimiter,
                                       char dayDelimiter,
                                       char hourDelimiter,
                                       boolean includeSeconds )
    {
        final long timeInMillis = 1000L * integerTime;
        theCalendar.setTime( new Date( timeInMillis ) );
        String year = the04dFormat.format( theCalendar.get( Calendar.YEAR ) );
        buffer.append( year );
        buffer.append( yearDelimiter );
        final int monthNumber = 1 + theCalendar.get( Calendar.MONTH );
        buffer.append( the02dFormat.format( monthNumber ) );
        buffer.append( yearDelimiter );
        appendCalendarField( buffer, Calendar.DAY_OF_MONTH );
        buffer.append( dayDelimiter );
        appendCalendarField( buffer, Calendar.HOUR_OF_DAY );
        buffer.append( hourDelimiter );
        appendCalendarField( buffer, Calendar.MINUTE );
        if ( includeSeconds )
        {
            buffer.append( hourDelimiter );
            appendCalendarField( buffer, Calendar.SECOND );
        }
    }

    /**
     * Append the contents of the specified calendar field, in the format
     *  "dd", to the specified buffer.
     *
     * @param buffer The buffer to which to append the field.
     *
     * @param field The field number, as defined by the Calendar class,
     *  of the field to append.
     */
    private void appendCalendarField( StringBuffer buffer, int field )
    {
        buffer.append( the02dFormat.format( theCalendar.get( field ) ) );
    }

    /**
     * Append the specified string field to the specified buffer.  The
     *  string is left-justified in a field which is the specified number
     *  of characters in length.
     *
     * @param buffer The buffer to which the append the specified string field.
     *
     * @param field The field being appended to the buffer.
     *
     * @param width The field width.
     */
    private void appendLeftJustifiedTo( StringBuffer buffer,
                                        String field,
                                        int width )
    {
        String slicedField = field;
        int slicedLength = slicedField.length();
        if ( slicedLength > width )
        {
            slicedField = field.substring( 0, width );
            slicedLength = width;
        }
        buffer.append( slicedField );
        for ( int idx = slicedLength; idx < width; idx += 1 )
        {
            buffer.append( ' ' );
        }
    }

    /**
     * Get a floating-point value from a field within a line of data.
     *
     * @param fieldString The field from which to extract the floating-point
     *  value.  The string is trimmed before extracting the value.
     *
     * @param fieldName The name of the field.  This is used for formatting
     *  an error message.  The field name should be capitalized.
     *
     * @param wholeLine The line containing the field.  This is used for
     *  formatting an error message.
     *
     * @return Value of the specified field.
     *
     * @exception MagDataFormatException If an error is detected in the
     *  format of the field.
     */
    private float getFloatFrom( String fieldString,
                                String fieldName,
                                String wholeLine )
        throws MagnetDataFormatException
    {
        try
        {
            return Float.parseFloat( fieldString.trim() );
        }
        catch ( NumberFormatException e )
        {
        	StringBuffer message = new StringBuffer();
        	message.append(fieldName + " field is not a valid floating-point number. ");
        	message.append("{fieldString=").append(fieldString).append("}");
        	message.append("{line=").append(wholeLine).append("}");
            throw new MagnetDataFormatException(message.toString());
        }
    }

    /**
     * Append the specified floating-point field to the specified buffer.
     *  The field is formatted in a manner which mimics the C % 9.3f format.
     *
     * @param buffer The buffer to which the append the specified field.
     *
     * @param value The floating-point value being appended to the buffer.
     */
    private void appendBlank93fTo( StringBuffer buffer, float value )
    {
        String myString = theBlank93fFormat.format( value );
        for ( int count = myString.length(); count < 9; count += 1 )
        {
            buffer.append( ' ' );
        }
        buffer.append( myString );
    }
}
